﻿/*
 * Copyright (c) 2011
 * Thomas Rozanski, Geoffrey Wright, Zachary Lynn, Jesse Natalie
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THOMAS ROZANSKI, GEOFFREY WRIGHT,
 * ZACHARY LYNN, OR JESSE NATALIE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Kinected;

#if RELEASE_TIMER
using System.Diagnostics;
#endif

namespace TowerDefense
{
    /// <summary>A simple Tower Defense game demo using the Kinected Framework.</summary>
    class TowerDefense
    {
        // Balls, towers, and projectiles
        private List<Enemy> enemies;
        private Dictionary<int, Tower> towers;
        private List<Projectile> projectiles;

        private List<Enemy> removeEnemies;
        private List<Projectile> removeProjectiles;

        // Tower defense-specific data
        private int maxEnemies;
        private int spawnedEnemies;
        private Path enemyPath;
        private int currentWave;
        private int totalWaves;
        private int playerHealth;

        // Texture for towers and balls
        private Texture2D enemyTexture;
        private Texture2D towerTexture;
        private Texture2D flashTexture;
        private Texture2D projectileTexture;
        private Texture2D backgroundTexture;

        // Texture widths
        private float ballWidth, towerWidth, flashWidth, projectileWidth;
        private Vector2 towerOrigin, projectileOrigin, flashOrigin;

        // Bounding game rectangle
        private Rectangle boardBounds;

        // Random
        private Random random;

        /// <param name="maxEnemies">Max number of enemies to spawn.</param>
        public TowerDefense(int maxEnemies)
        {
			this.maxEnemies = maxEnemies;
            spawnedEnemies = 0;
            currentWave = 0;
            totalWaves = 3;
            playerHealth = 10;

            enemyPath = new Path(new Vector2(220, -50));
            enemyPath.AddPathPosition(new Vector2(220, 230));
            enemyPath.AddPathPosition(new Vector2(670, 230));
            enemyPath.AddPathPosition(new Vector2(670, 108));
            enemyPath.AddPathPosition(new Vector2(1054, 108));
            enemyPath.AddPathPosition(new Vector2(1054, 578));
            enemyPath.AddPathPosition(new Vector2(0, 578));

            boardBounds = new Rectangle(0, 0, 1280, 720);

            random = new Random();

            enemies = new List<Enemy>();
            towers = new Dictionary<int, Tower>();
            projectiles = new List<Projectile>();

            removeEnemies = new List<Enemy>();
            removeProjectiles = new List<Projectile>();
        }

        /// <summary>Load the Tower Defense assets.</summary>
        public void LoadContent(ContentManager Content)
        {
            // Load textures
            enemyTexture = Content.Load<Texture2D>("enemy");
            towerTexture = Content.Load<Texture2D>("towers/tower");
            flashTexture = Content.Load<Texture2D>("towers/flash");
            projectileTexture = Content.Load<Texture2D>("projectiles/bullet");
            backgroundTexture = Content.Load<Texture2D>("backgrounds/background");

            // Set widths
            ballWidth = enemyTexture.Width * 0.9f;
            towerWidth = towerTexture.Width;
            flashWidth = flashTexture.Width;
            projectileWidth = projectileTexture.Width * 0.1f;

            towerOrigin = new Vector2(towerTexture.Width / 2, towerTexture.Height / 2);
            projectileOrigin = new Vector2(projectileTexture.Width / 2, projectileTexture.Height / 2);
            flashOrigin = new Vector2(flashTexture.Width/2, flashTexture.Height/2);
        }

        /// <summary>Adds and removes towers given the new state of the board.</summary>
        private void UpdateTowers(Board board)
        {
            // Remove dead towers
            List<int> removedKeys = board.RemovedKeys();

            for (int i = 0; i < removedKeys.Count; i++)
            {
                // Tower explode animation here
                towers.Remove(removedKeys[i]);
            }

            // Add new towers
            List<int> addedKeys = board.AddedKeys();

            for (int i = 0; i < addedKeys.Count; i++)
            {
                Circle circle = board.GetCircle(addedKeys[i]);
                bool collidesWithPath = false;
                
                // Tower add animation here
                for (int j = 0; j < enemyPath.Size()-1; j++)
                {
                    Vector2 segv = enemyPath.GetPosition(j + 1) - enemyPath.GetPosition(j);
                    segv.Normalize();
                    Vector2 ptv = circle.Center - enemyPath.GetPosition(j);
                    float projv = Vector2.Dot(ptv, segv);

                    Vector2 closest;
                    if (projv < 0.0f) { closest = enemyPath.GetPosition(j); }
                    else { closest = enemyPath.GetPosition(j + 1); }

                    Vector2 projV = projv * segv;
                    closest = enemyPath.GetPosition(j) + projV;

                    Vector2 distV = circle.Center - closest;

                    if (distV.Length() < circle.Radius)
                    {
                        collidesWithPath = true;
                    }
                }
                
                if (!collidesWithPath)
                {
                    towers.Add(addedKeys[i], new Tower(circle, towerWidth, flashWidth));
                }
            }
        }

        /// <summary>Update the game's state.</summary>
        public void Update(GameTime gameTime, Board board)
        {

#if RELEASE_TIMER
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
#endif
            // Loss condition
            if (playerHealth < 1)
            {
                Console.WriteLine("YOU LOSE");
            }

            // Go to the next wave
            if (enemies.Count == 0 && currentWave < totalWaves)
            {
                currentWave++;
                spawnedEnemies = 0;
            }

            if (spawnedEnemies < maxEnemies)
            {
                // Make sure no balls are close to the spawn point
                bool canSpawn = true;

                for (int i = 0; i < enemies.Count; i++)
                {
                    if (Vector2.Distance(enemyPath.GetPosition(0), enemies[i].Position) < 100f)
                    {
                        canSpawn = false;
                        break;
                    }
                }

                // If no balls were near, spawn
                if (canSpawn)
                {
                    Vector2 velocity = enemyPath.GetPosition(1) - enemyPath.GetPosition(0);
                    velocity.Normalize();
                    enemies.Add(new Enemy(enemyPath.GetPosition(0), ballWidth / 2, velocity));
                    spawnedEnemies++;
                }
            }

            // Update the towers
            UpdateTowers(board);

            int stepsPerFrame = 25;
            float timeStep = (float)gameTime.ElapsedGameTime.TotalSeconds / stepsPerFrame;

            for (int step = 0; step < stepsPerFrame; step++)
            {
                // Update the balls
                for (int i = 0; i < enemies.Count; i++)
                {
                    enemies[i].Update(timeStep);

                    // Enemy unit path finding
                    Vector2 ballPosition = enemies[i].Position;
                    Vector2 targetPosition = enemyPath.GetPosition(enemies[i].PathSegment);

                    if (Math.Abs(ballPosition.X - targetPosition.X) < 0.1f &&
                        Math.Abs(ballPosition.Y - targetPosition.Y) < 0.1f)
                    {
                        enemies[i].Position = targetPosition;
                        enemies[i].PathSegment++;

                        // If the ball has another path segment, send it along it
                        if (enemyPath.HasNextPosition(enemies[i].PathSegment))
                        {
                            Vector2 newVelocity = enemyPath.GetPosition(enemies[i].PathSegment) - enemies[i].Position;
                            newVelocity.Normalize();
                            enemies[i].Velocity = newVelocity;
                        }

                        // Otherwise destroy the ball, since it reached the end
                        else
                        {
                            removeEnemies.Add(enemies[i]);

                            // Hit the end of the board
                            playerHealth--;
                        }
                    }

                    // Resolve collisions with projectiles
                    foreach (Projectile projectile in projectiles)
                    {
                        // Check for ball to projectile collision
                        if (Circle.Intersection(enemies[i].Bounds, projectile.Bounds))
                        {
                            enemies[i].ProjectileCollision();
                            removeProjectiles.Add(projectile);
                        }

                        // Remove the projectile if it leaves the viewspace to avoid buildup
                        if (projectile.Position.X < boardBounds.Left ||
                            projectile.Position.X + projectileWidth > boardBounds.Right ||
                            projectile.Position.Y < boardBounds.Top ||
                            projectile.Position.Y + projectileWidth > boardBounds.Bottom)
                        {
                            removeProjectiles.Add(projectile);
                        }
                    }

                    // Remove the projectiles from the list
                    foreach (Projectile projectile in removeProjectiles)
                    {
                        projectiles.Remove(projectile);
                    }
                    removeProjectiles.Clear();

                    if (enemies[i].Health <= 0)
                    {
                        removeEnemies.Add(enemies[i]);
                    }
                }

                // Remove the balls from the list
                foreach (Enemy ball in removeEnemies)
                {
                    enemies.Remove(ball);
                }
                removeEnemies.Clear();
            }

            // Update towers
            foreach (Tower tower in towers.Values)
            {
                tower.Update();

                if (enemies.Count > 0 && tower.FireProjectile())
                {
                    int closestBall = 0;
                    float closestDistance = float.MaxValue;

                    // Find the closest ball
                    for (int i = 0; i < enemies.Count; i++)
                    {
                        float currentDistance = Vector2.Distance(enemies[i].Position, tower.Position);

                        if (currentDistance < closestDistance)
                        {
                            closestDistance = currentDistance;
                            closestBall = i;
                        }
                    }

                    // Fire projectile at the closest ball
                    Vector2 projectileDirection = enemies[closestBall].Position - tower.Position;
                    projectileDirection.Normalize();
                    projectiles.Add(new Projectile(tower.Position + projectileOrigin, projectileWidth / 2, projectileDirection));
                }
            }

            // Update the projectiles
            for (int i=0; i<projectiles.Count; i++)
            {
                projectiles[i].Update(timeStep);
            }

#if RELEASE_TIMER
            stopWatch.Stop();
            Console.WriteLine("TowerDefense.Update: {0}ms", stopWatch.Elapsed.TotalMilliseconds);
#endif
        }

        /// <summary>Draw Tower Defense to the screen.</summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(backgroundTexture, new Rectangle(0, 0, backgroundTexture.Width, backgroundTexture.Height), Color.White);

            // Draw projectiles
            foreach (Projectile projectile in projectiles)
            {
                spriteBatch.Draw(projectileTexture, projectile.Position, null, Color.White, projectile.Rotation, projectileOrigin, 1.0f, SpriteEffects.None, 0);
            }

            // Draw balls
            for (int i = 0; i < enemies.Count; i++)
            {
                spriteBatch.Draw(enemyTexture, enemies[i].Position, Color.White);
            }

            // Draw towers
            foreach (Tower tower in towers.Values)
            {
                spriteBatch.Draw(towerTexture, tower.Position, null, Color.White, 0.0f, towerOrigin, tower.TowerScale, SpriteEffects.None, 0);
                spriteBatch.Draw(flashTexture, tower.Position, null, Color.White * tower.FlashAlpha, tower.Rotation, flashOrigin, tower.TowerScale, SpriteEffects.None, 0);
            }
        }
    }
}
